Popular Searches
Popular Course Categories
Popular Courses

Working with Comparable and Comparator

Working with Comparable and Comparator

Maps, Iterators, and Collection Utilities

Working with Comparable and Comparator in Java

Course: Core Java Training

Topic: Working with Comparable and Comparator

Course Link: Core Java Training Course

Demo Link: Register for a Free Course Demo

JustAcademy Website: Visit JustAcademy


1. Introduction

Sorting is an important operation in Java programming. Applications often need to arrange numbers, names, products, employees, students, or other objects in a particular order. Java provides two key interfaces for defining object-sorting rules: Comparable and Comparator.

The Comparable interface defines the natural ordering of objects. A class implements this interface when it has a default comparison rule, such as sorting students by roll number or products by product ID.

The Comparator interface defines an external or alternative comparison rule. It allows developers to sort the same objects in different ways without modifying the class itself, such as sorting employees by salary, name, or joining date.

Both interfaces are part of the java.lang and java.util packages respectively. They are commonly used with sorting methods, lists, and sorted collections such as TreeSet and TreeMap.

2. Learning Objectives

  • Understand the purpose of object comparison and sorting in Java.
  • Learn the structure and methods of Comparable.
  • Implement the compareTo() method to define natural ordering.
  • Understand the structure and methods of Comparator.
  • Write custom comparators using classes, anonymous classes, and lambda expressions.
  • Sort objects in ascending and descending order.
  • Compare objects using multiple fields.
  • Understand how comparison affects TreeSet and TreeMap.
  • Identify common sorting mistakes and apply best practices.

3. What Is Object Comparison?

Object comparison determines the relative order of two objects. For example, when sorting employee records, Java needs a rule to decide whether one employee should appear before or after another employee.

For numbers, comparison is straightforward. For custom objects, Java needs a comparison rule based on one or more fields.

Consider an Employee class containing the following fields:

class Employee {
    int id;
    String name;
    double salary;
}

To sort employees, you must decide which field determines their order. Possible rules include:

  • Employee ID in ascending order.
  • Name in alphabetical order.
  • Salary from lowest to highest.
  • Salary from highest to lowest.
  • Salary first, followed by name when salaries are equal.

Comparable and Comparator provide ways to define these rules.

4. Understanding the Comparable Interface

Comparable is a generic interface in the java.lang package. It is used when a class defines its own natural ordering.

Its main method is:

public interface Comparable<T> {
    int compareTo(T other);
}

The class implementing Comparable must provide the compareTo() method. This method compares the current object with another object of the same or compatible type.

Return Values of compareTo()

Return Value Meaning Sorting Interpretation
Negative integer The current object is less than the other object according to the comparison rule. The current object comes before the other.
Zero The objects are equal according to the comparison rule. Neither object is ordered before the other.
Positive integer The current object is greater than the other object according to the comparison rule. The current object comes after the other.

The method does not need to return exactly -1 or 1. Any negative integer, zero, or positive integer is valid.

5. Implementing Comparable in a Java Class

Suppose a student has a roll number and a name. If the natural ordering should be based on roll number, the Student class can implement Comparable<Student>.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Student implements Comparable<Student> {
    private int rollNumber;
    private String name;

    public Student(int rollNumber, String name) {
        this.rollNumber = rollNumber;
        this.name = name;
    }

    public int getRollNumber() {
        return rollNumber;
    }

    public String getName() {
        return name;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.rollNumber, other.rollNumber);
    }

    @Override
    public String toString() {
        return rollNumber + " - " + name;
    }
}

public class ComparableExample {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();

        students.add(new Student(103, "Aarav"));
        students.add(new Student(101, "Priya"));
        students.add(new Student(102, "Rahul"));

        Collections.sort(students);

        for (Student student : students) {
            System.out.println(student);
        }
    }
}

Output:

101 - Priya
102 - Rahul
103 - Aarav

Explanation: The Student class implements Comparable<Student>. Its compareTo() method compares roll numbers. Calling Collections.sort() sorts the list using that natural ordering.

6. Understanding the compareTo() Method

The compareTo() method determines the natural order of objects. A good implementation should follow a consistent comparison contract.

Example: Comparing Integer Values

public class CompareToExample {
    public static void main(String[] args) {
        Integer first = 10;
        Integer second = 20;

        System.out.println(first.compareTo(second));
        System.out.println(second.compareTo(first));
        System.out.println(first.compareTo(10));
    }
}

Output:

-1
1
0

The exact negative or positive number is not guaranteed for every implementation. The important distinction is whether the result is negative, zero, or positive.

Using Integer.compare() Instead of Subtraction

When comparing integer fields, use Integer.compare() instead of subtracting one value from another.

// Recommended
@Override
public int compareTo(Student other) {
    return Integer.compare(this.rollNumber, other.rollNumber);
}

// Avoid this approach
@Override
public int compareTo(Student other) {
    return this.rollNumber - other.rollNumber;
}

Subtraction can overflow when values are very large or very small, producing an incorrect comparison result. Similar helper methods include Long.compare(), Double.compare(), and Boolean.compare().

7. Sorting Strings and Other Comparable Types

Java's standard classes, including String, Integer, Double, and many date and time classes, already define natural ordering.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class NaturalOrderExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("Riya");
        names.add("Aarav");
        names.add("Kabir");

        Collections.sort(names);

        System.out.println(names);
    }
}

Output:

[Aarav, Kabir, Riya]

For strings, natural ordering is lexicographical and case-sensitive. It is not necessarily the same as dictionary ordering for human languages.

8. What Is the Comparator Interface?

Comparator is a functional interface in the java.util package. It allows a developer to define an ordering independently of the class being sorted.

Its main abstract method is:

int compare(T first, T second);

Unlike Comparable, which compares the current object with another object, Comparator receives both objects as arguments.

Return Values of compare()

Return Value Meaning
Negative integer The first argument should appear before the second.
Zero The arguments are equivalent under this comparator's ordering.
Positive integer The first argument should appear after the second.

9. Implementing Comparator Using a Separate Class

Suppose students have a roll number and a name. The class already defines its natural order by roll number, but an application also needs to sort students alphabetically. A separate comparator can provide that alternative ordering.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class StudentNameComparator implements Comparator<Student> {
    @Override
    public int compare(Student first, Student second) {
        return first.getName().compareTo(second.getName());
    }
}

public class ComparatorClassExample {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();

        students.add(new Student(103, "Rahul"));
        students.add(new Student(101, "Aarav"));
        students.add(new Student(102, "Priya"));

        Collections.sort(students, new StudentNameComparator());

        for (Student student : students) {
            System.out.println(student);
        }
    }
}

Output:

101 - Aarav
102 - Priya
103 - Rahul

Here, StudentNameComparator determines the order by name. The existing natural ordering of the Student class remains unchanged.

Note: The Student class used here is the same class defined in Section 5, where compareTo() sorts by roll number.

10. Using an Anonymous Comparator

An anonymous class lets you define a comparator directly where it is needed without creating a separate named class.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class AnonymousComparatorExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("Neha");
        names.add("Aarav");
        names.add("Priya");

        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String first, String second) {
                return Integer.compare(first.length(), second.length());
            }
        });

        System.out.println(names);
    }
}

Output:

[Neha, Priya, Aarav]

The comparator sorts the strings by length. When two names have the same length, this comparator considers them equal for sorting purposes; their relative order is not guaranteed by the comparator itself.

11. Using Comparator with Lambda Expressions

Because Comparator is a functional interface, Java supports concise lambda expressions for defining comparison rules.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class LambdaComparatorExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("Neha");
        names.add("Aarav");
        names.add("Priya");

        names.sort((first, second) ->
            Integer.compare(first.length(), second.length())
        );

        System.out.println(names);
    }
}

Output:

[Neha, Priya, Aarav]

The expression (first, second) -> ... implements the comparator's compare() method. Lambda expressions make short, local sorting rules easier to read.

12. Using Comparator.comparing()

Java provides factory methods such as Comparator.comparing(), comparingInt(), comparingLong(), and comparingDouble(). These methods create comparators based on a selected field or value.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return name + " - " + price;
    }
}

public class ComparingExample {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>();

        products.add(new Product("Keyboard", 1200));
        products.add(new Product("Mouse", 500));
        products.add(new Product("Monitor", 8500));

        products.sort(Comparator.comparingDouble(Product::getPrice));

        products.forEach(System.out::println);
    }
}

Output:

Mouse - 500.0
Keyboard - 1200.0
Monitor - 8500.0

The method reference Product::getPrice extracts each product's price. The comparator then orders products by that value in ascending order.

13. Sorting in Descending Order

To reverse an existing comparator, call reversed(). This is generally easier to understand than manually reversing the comparison logic.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class DescendingOrderExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();

        numbers.add(40);
        numbers.add(10);
        numbers.add(30);
        numbers.add(20);

        numbers.sort(Comparator.reverseOrder());

        System.out.println(numbers);
    }
}

Output:

[40, 30, 20, 10]

For custom objects, you can also reverse a comparator created using Comparator.comparing().

products.sort(
    Comparator.comparingDouble(Product::getPrice).reversed()
);

This sorts products from the highest price to the lowest price.

14. Sorting Objects by Multiple Fields

Sometimes one field is not sufficient to determine a complete order. For example, employees may be sorted by department first and name second. The thenComparing() method allows multiple sorting criteria to be combined.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

class Employee {
    private int id;
    private String name;
    private String department;
    private double salary;

    public Employee(int id, String name, String department,
                    double salary) {
        this.id = id;
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getDepartment() {
        return department;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return id + " - " + name + " - " + department
               + " - " + salary;
    }
}

public class MultipleFieldSorting {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();

        employees.add(new Employee(1, "Riya", "IT", 60000));
        employees.add(new Employee(2, "Aarav", "HR", 45000));
        employees.add(new Employee(3, "Kabir", "IT", 55000));
        employees.add(new Employee(4, "Neha", "HR", 48000));

        Comparator<Employee> byDepartmentThenName =
            Comparator.comparing(Employee::getDepartment)
                      .thenComparing(Employee::getName);

        employees.sort(byDepartmentThenName);

        employees.forEach(System.out::println);
    }
}

Output:

2 - Aarav - HR - 45000.0
4 - Neha - HR - 48000.0
3 - Kabir - IT - 55000.0
1 - Riya - IT - 60000.0

The first comparison sorts employees by department. If two employees belong to the same department, the second comparison sorts them by name.

15. Sorting with a Primary and Secondary Numeric Field

Numeric fields can be combined with methods such as comparingInt() and thenComparingDouble().

Comparator<Employee> bySalaryThenName =
    Comparator.comparingDouble(Employee::getSalary)
              .thenComparing(Employee::getName);

employees.sort(bySalaryThenName);

This example sorts employees by salary in ascending order. Employees with equal salaries are then ordered alphabetically by name.

For descending salary with ascending name, reverse only the salary comparison before adding the secondary criterion:

Comparator<Employee> bySalaryDescendingThenName =
    Comparator.comparingDouble(Employee::getSalary)
              .reversed()
              .thenComparing(Employee::getName);

16. Sorting with Arrays.sort() and List.sort()

Java supports sorting arrays and lists with natural ordering or a supplied comparator.

Sorting an Array

import java.util.Arrays;
import java.util.Comparator;

public class ArraySortingExample {
    public static void main(String[] args) {
        String[] names = {"Riya", "Aarav", "Kabir"};

        Arrays.sort(names);

        System.out.println(Arrays.toString(names));

        Arrays.sort(names, Comparator.reverseOrder());

        System.out.println(Arrays.toString(names));
    }
}

Output:

[Aarav, Kabir, Riya]
[Riya, Kabir, Aarav]

Sorting a List

List<Integer> numbers =
    new ArrayList<>(List.of(7, 2, 9, 1));

numbers.sort(Comparator.naturalOrder());
System.out.println(numbers);

numbers.sort(Comparator.reverseOrder());
System.out.println(numbers);

Output:

[1, 2, 7, 9]
[9, 7, 2, 1]

List.of() is available in Java 9 and later. The example creates a mutable ArrayList because the immutable list returned by List.of() cannot be sorted in place.

17. Comparable and Comparator with TreeSet

TreeSet maintains elements in sorted order. It uses natural ordering when elements implement Comparable, or it can use an explicitly supplied Comparator.

import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetComparatorExample {
    public static void main(String[] args) {
        TreeSet<String> names = new TreeSet<>();

        names.add("Riya");
        names.add("Aarav");
        names.add("Kabir");

        System.out.println(names);

        TreeSet<String> reverseNames =
            new TreeSet<>(Comparator.reverseOrder());

        reverseNames.add("Riya");
        reverseNames.add("Aarav");
        reverseNames.add("Kabir");

        System.out.println(reverseNames);
    }
}

Output:

[Aarav, Kabir, Riya]
[Riya, Kabir, Aarav]

Important: A TreeSet uses the comparison result to determine whether elements occupy the same position in its ordering. If the comparator returns zero for two different objects, the set treats them as duplicates for insertion purposes. Design comparison rules carefully, especially when object equality is based on different fields.

18. Comparable and Comparator with TreeMap

TreeMap sorts its keys using their natural ordering or a supplied comparator. Keys that compare as equal are treated as the same key by the map.

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

public class TreeMapComparatorExample {
    public static void main(String[] args) {
        Map<String, Integer> scores =
            new TreeMap<>(Comparator.reverseOrder());

        scores.put("Aarav", 85);
        scores.put("Priya", 92);
        scores.put("Kabir", 78);

        scores.forEach((name, score) ->
            System.out.println(name + " : " + score));
    }
}

Output:

Priya : 92
Kabir : 78
Aarav : 85

The reverse-order comparator arranges the string keys in descending natural order. The values are not used to determine the map's order.

19. Key Differences Between Comparable and Comparator

Feature Comparable Comparator
Package java.lang java.util
Main method compareTo(T other) compare(T first, T second)
Comparison location Implemented inside the class being compared Defined separately from the class being compared
Purpose Defines natural ordering Defines alternative or custom ordering
Number of ordering rules Usually one natural ordering per class Multiple comparators can define different orderings
Modifying the original class The class must implement the interface Usually not required
Lambda usage Not typically used to implement a class's natural ordering Can be implemented using a lambda expression
Common usage Collections.sort(list) when elements have natural ordering list.sort(comparator) for a chosen ordering

20. Comparison Contract and Consistency

Comparison methods must produce consistent results. If a comparison rule is inconsistent, sorting algorithms and sorted collections may behave unexpectedly.

Both compareTo() and compare() should satisfy these core rules:

  • Sign symmetry: If comparing A with B produces a negative result, comparing B with A should produce a positive result, and vice versa.
  • Transitivity: If A comes before B and B comes before C, A should come before C.
  • Consistency of equivalent comparisons: If A and B compare as equal, their comparisons against another object should have consistent signs.
  • Consistency with equals: It is strongly recommended that a natural ordering treats two objects as equal exactly when equals() does. Comparators should also be consistent with equality when used with sorted sets and maps, where practical.

A comparator is allowed to define an ordering that differs from equals(), but this can cause surprising behavior in TreeSet and TreeMap. For example, comparing employees only by department means employees in the same department may compare as zero, even if they are different employees.

21. Common Mistakes and Their Solutions

Mistake Problem Solution
Using subtraction to compare integers Integer overflow may cause incorrect ordering. Use Integer.compare() or Long.compare().
Confusing compareTo() with compare() The wrong method signature may be implemented. Use compareTo(other) for Comparable and compare(first, second) for Comparator.
Returning only positive values The comparison contract is violated. Return negative, zero, or positive values according to the comparison.
Comparing only one field unintentionally Distinct objects may compare as equal. Add secondary comparisons when required.
Assuming sorting is always ascending A custom comparator may reverse or change the order. Check the comparator's intended behavior.
Using null fields without handling them Comparisons may throw NullPointerException. Validate data or use null-aware comparators such as Comparator.nullsFirst().
Assuming TreeSet retains every different object Elements comparing as zero may be treated as duplicates. Choose an ordering that distinguishes entries when needed.
Changing fields used for comparison while stored in a TreeSet or TreeMap The collection's ordering can become inconsistent with its structure. Avoid mutating ordering fields while objects are stored in sorted collections.

22. Handling Null Values in Comparators

If a list may contain null elements, a comparator can explicitly specify how null values should be ordered.

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class NullComparatorExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("Priya");
        names.add(null);
        names.add("Aarav");

        names.sort(Comparator.nullsFirst(
            Comparator.naturalOrder()));

        System.out.println(names);
    }
}

Output:

[null, Aarav, Priya]

Comparator.nullsFirst() places null values before non-null values. Use Comparator.nullsLast() to place them after non-null values. Null handling for fields inside objects must also be considered separately.

23. Best Practices for Comparable and Comparator

  • Use Comparable when your class has one clear, meaningful natural ordering.
  • Use Comparator when the same objects need to be sorted in multiple ways.
  • Prefer Comparator.comparing() and its numeric variants for readable sorting code.
  • Use thenComparing() to define secondary and additional sorting criteria.
  • Use reversed() to reverse an existing ordering.
  • Use Integer.compare() and other type-specific comparison methods rather than subtraction.
  • Make sure comparison logic is transitive and consistent.
  • Consider how comparison results interact with equals(), especially in sorted sets and maps.
  • Handle null values explicitly if they are allowed in the data.
  • Keep the fields used for sorting stable while objects are stored in sorted collections.
  • Use generics to make comparison code type-safe.

24. Practical Exercise

Task: Create an employee management program that supports multiple sorting options.

  1. Create an Employee class with employee ID, name, department, and salary.
  2. Implement Comparable<Employee> to sort employees by employee ID.
  3. Create a comparator to sort employees by name alphabetically.
  4. Create another comparator to sort employees by salary from highest to lowest.
  5. Sort employees by department and then by name.
  6. Display the results for each sorting rule.
  7. Test the program with employees having the same salary or department.
  8. Verify that comparison results are consistent for all test records.

Challenge Questions

  • How would you sort employees by salary ascending and then by name descending?
  • How would you sort names without distinguishing uppercase and lowercase letters?
  • What could happen if two different employees compare as zero in a TreeSet?
  • How can you handle a missing or null employee name?

25. Interview Questions and Answers

Q1. What is Comparable in Java?

Answer: Comparable is an interface that allows a class to define its natural ordering through the compareTo() method.

Q2. What is Comparator in Java?

Answer: Comparator is a functional interface that defines an ordering through the compare() method. It allows custom sorting without requiring the class being sorted to implement the comparison rule.

Q3. What is the difference between compareTo() and compare()?

Answer: compareTo() compares the current object with another object. compare() accepts two objects and compares them.

Q4. Can one class have multiple sorting rules?

Answer: Yes. A class can define a natural ordering using Comparable and support multiple additional orderings using separate Comparator implementations.

Q5. How do you sort a list in descending order?

Answer: Use list.sort(Comparator.reverseOrder()) for naturally comparable elements or call reversed() on a suitable custom comparator.

Q6. What is Comparator.comparing()?

Answer: It creates a comparator based on a selected field or extracted key, such as employee name, product price, or student roll number.

Q7. What is thenComparing() used for?

Answer: It adds a secondary comparison rule that is used when the primary comparison returns zero.

Q8. Why should integer subtraction be avoided in compareTo()?

Answer: Subtracting large integer values can overflow and produce an incorrect comparison result. Integer.compare() is safer.

Q9. Can a Comparator be implemented with a lambda expression?

Answer: Yes. Comparator is a functional interface, so a lambda expression can implement its single abstract method.

Q10. How do Comparable and Comparator affect TreeSet?

Answer: A TreeSet uses natural ordering or a supplied comparator to sort elements. Elements that compare as zero are treated as duplicates by the set.

Q11. What does it mean for a comparator to be consistent with equals?

Answer: It means that two objects compare as zero if and only if their equals() method considers them equal. This is recommended for predictable behavior in sorted sets and maps.

Q12. How can null values be handled during sorting?

Answer: Use Comparator.nullsFirst() or Comparator.nullsLast() to define where null elements should appear.

26. Quick Revision Checklist

  • Comparable defines natural ordering with compareTo().
  • Comparator defines custom ordering with compare().
  • A negative result means before, zero means equivalent under the comparison, and a positive result means after.
  • Collections.sort(), List.sort(), and Arrays.sort() support sorting.
  • Comparator.comparing() sorts using an extracted key.
  • thenComparing() adds secondary sorting criteria.
  • reversed() reverses the comparator's ordering.
  • Use Integer.compare() instead of integer subtraction.
  • Comparison logic should be consistent and transitive.
  • Sorted collections treat elements or keys that compare as zero as equivalent for their ordering.
  • Use null-aware comparators when null elements are possible.

27. Conclusion

Comparable and Comparator are essential interfaces for sorting objects in Java. Comparable provides a class's natural ordering, while Comparator offers flexible alternatives without changing the original class. By understanding compareTo(), compare(), lambda expressions, Comparator.comparing(), and thenComparing(), developers can implement readable and reliable sorting logic for lists, arrays, and sorted collections.

To continue learning Java collections, object-oriented programming, and other Core Java concepts, visit JustAcademy's Core Java Training Course, register for a course demo, or explore the JustAcademy website.

28. Further Reading

Previous Next
whatsapp